﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Dungeon_Dive
{
    public enum Elements { Earth, Water, Air, Fire }

    /// <summary>
    /// Base class for all mobile interacting entities with 3D models in Dungeon Dive
    /// </summary>
    public class GameEntity : AnimModel
    {
        private readonly int MAXTOKENS = 5;

        Game parentGame;        
        SpriteFont debugfont;

        Log log;

        protected Random rand;

        public Vector3 Position { get; set; }   // Entity's position in 3D space
        public Vector3 TilePosition
        {
            get { return new Vector3((float)Math.Floor(Position.X / 200), 0, (float)Math.Floor(Position.Z / 200)); }
        }
        public float Scale { get; set; }
        public float Rotation { get; set; }

        private Matrix world;
        public Matrix World
        {
            get { return world * Matrix.CreateScale(Scale)
                * Matrix.CreateRotationY(Rotation)
                * Matrix.CreateTranslation(Position); }
        }

        private BoundingSphere boundingSphere;
        public BoundingSphere BoundingSphere
        {
            get
            {
                //return boundingSphere.Transform(World);
                return boundingSphere.Transform(world
                    * Matrix.CreateScale(Scale) * Matrix.CreateTranslation(Position));

                //I think this solves the problem of critters getting stuck in walls.
                //If entities aren't quite symmetrical turning gives
                //them the opportunity to clip their bsphere into a wall.
                //So rotation is no longer applied to bsphere.
            }
        }

        public int AttackType { get; protected set; }   // Type of entity's attack

        protected Stack<Elements> tokens;   // Entity's collection of elemental tokens
        
        /// <summary>
        /// GameEntity constuctor        
        /// </summary>
        /// <param name="game"></param>
        public GameEntity(Game game) : base(game)
        {
            parentGame = game;

            world = Matrix.Identity;

            rand = (Random)game.Services.GetService(typeof(Random));

            tokens = new Stack<Elements>();

            boundingSphere = new BoundingSphere();

            AttackType = -1;    // default non-elemental attack

            log = (Log)game.Services.GetService(typeof(Log));            
        }

        public override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            //ContentManager contentManager = (ContentManager)parentGame.Services.GetService(typeof(ContentManager));
            //sb = contentManager.SpriteBatch;
            //debugfont = parentGame.Content.Load<SpriteFont>("Fonts\\Debugfont");
            //PROBLEM: SpriteBatch needed before contentManager initialized
            base.LoadContent();
        }

        /// <summary>
        /// GameEntity's Draw only used for debug texts.
        /// Should maybe write them to console instead?
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Draw(GameTime gameTime)
        {
            ContentManager contentManager = (ContentManager)parentGame.Services.GetService(typeof(ContentManager));
            SpriteBatch sb = contentManager.SpriteBatch;
            debugfont = parentGame.Content.Load<SpriteFont>("Fonts\\Debugfont");

            string ts = "";            

            foreach (Elements el in tokens)
            {                
                ts += el.ToString() + " ";
            }            

            sb.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront,
                SaveStateMode.SaveState);
            /*sb.DrawString(debugfont, "Character " + Position.ToString(),
                new Vector2(10, 50), Color.White);*/
            /*sb.DrawString(debugfont, "BoundingSphere " + BoundingSphere.ToString(),
                new Vector2(10, 70), Color.White);*/
            sb.DrawString(debugfont, "Tokens: " + ts,
                new Vector2(10, 90), Color.White);
            sb.DrawString(debugfont, "Attacktype: " + AttackType,
                new Vector2(10, 110), Color.White);
            sb.End();

            base.Draw(gameTime);
        }

        /// <summary>
        /// Initializes entity's boundingsphere.
        /// </summary>
        protected void CreateBoundingSphere()
        {
            BoundingSphere bs = new BoundingSphere(Vector3.Zero, 0.0f);
            Matrix[] boneTransforms = new Matrix[Model.Bones.Count];
            Model.CopyAbsoluteBoneTransformsTo(boneTransforms);
            foreach (ModelMesh mesh in Model.Meshes)
            {
                bs = BoundingSphere.CreateMerged(bs, mesh.BoundingSphere);
                // hope this is right...
                bs = bs.Transform(boneTransforms[mesh.ParentBone.Index] * World);
            }
            boundingSphere = bs;
        }

        /// <summary>
        /// This method picks a random element
        /// </summary>
        /// <returns>random Elements object</returns>
        protected Elements RandomElement()
        {            
            int e = rand.Next(4);
            return (Elements)e;
        }

        /// <summary>
        /// Adds an elemental token to entity's pool
        /// </summary>
        /// <param name="t">Element to be added</param>
        public void AddToken(Elements t)
        {
            if (tokens.Count < MAXTOKENS)
                tokens.Push(t);
            UpdateAttackType();
        }

        /// <summary>
        /// What happens when this GameEntity is hit in combat
        /// </summary>
        /// <param name="type">type of attack: -1 generic, 0-3 elemental attacks</param>
        /// <returns>true if entity should die</returns>
        public virtual bool HandleHit(int type)
        {
            if (tokens.Count <= 0)  // Entity at the brink of death
                return true;

            if (type < 0 || type >= 4)  // non-elemental attack does 1 token's worth of damage
            {
                tokens.Pop();
                UpdateAttackType();
                return false;
            }
            else
            {
                float strength = 2.0f;  // elemental attacks do 2 token's worth of base damage
                Elements attack = (Elements)Enum.Parse(typeof(Elements),type.ToString());
                Elements next;

                while (strength >= 1.0f && tokens.Count > 0)
                {
                    next = tokens.Peek();
                    if (IsOpposed(attack, next))
                    {
                        strength -= 0.66f;  // opposed element soaks less damage
                        tokens.Pop();
                    }
                    else if (attack == next)
                    {
                        //log.Write(attack + " on " + next + ", strength " + strength);
                        if (strength > 1.5f)    // I tend to use safe inequalities with floats
                        {                            
                            tokens.Pop();
                        }
                        strength -= 2.0f;   // like element soaks more damage
                    }
                    else
                    {
                        strength -= 1.0f;
                        tokens.Pop();
                    }
                }

                if (tokens.Count <= 0 && strength > 0.1f)
                    return true;
                else if (strength >= .66 && IsOpposed(attack, tokens.Peek()))
                {
                    tokens.Pop();
                }

                UpdateAttackType();

                return false;
            }
        }

        /// <summary>
        /// Helper function to determine if two elements are opposed for damage calculation
        /// </summary>
        /// <param name="attack">elementel type of attack</param>
        /// <param name="token">element being attacked</param>
        /// <returns></returns>
        protected bool IsOpposed(Elements attack, Elements token)
        {
            if (Math.Abs((int)attack - (int)token) == 2)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Sets entity's attack type according to its elemental token collection
        /// </summary>
        private void UpdateAttackType()
        {
            if (tokens.Count < 3)
            {
                AttackType = -1;
                return;
            }
            int[] counts = new int[4];

            foreach (Elements e in tokens)
            {
                counts[(int)e]++;
            }
            for (int i = 0; i < 4; ++i)
            {
                if (counts[i] >= 3)
                {
                    AttackType = i;
                    return;
                }
            }
            AttackType = -1;
        }

        /// <summary>
        /// Returns the volume entity's attack affects
        /// </summary>
        /// <returns></returns>
        public BoundingSphere GetAttackVolume()
        {
            Vector3 orig = BoundingSphere.Center;

            return new BoundingSphere(new Vector3(
                    orig.X + (float)(100f * Math.Sin(Rotation)),
                    100,
                    orig.Z + (float)(100f * Math.Cos(Rotation))),
                    75);
            //Currently a little generous
        }
        
    }
}
